package com.xscha.soyue.starter.web.handler;

import com.xscha.soyue.common.core.domain.R;
import com.xscha.soyue.common.core.exception.SoyRuntimeException;
import com.xscha.soyue.common.core.exception.status.CommonStatusCode;
import com.xscha.soyue.common.core.exception.status.SoyStatusCode;
import com.xscha.soyue.common.core.utils.StatusCodeUtils;
import com.xscha.soyue.starter.web.enums.SoyMvcStatusCode;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.server.ServerErrorException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.Objects;

@Slf4j
@RestControllerAdvice
@Order(10)
public class SoyWebmvcExceptionHandler {

	/**
	 * 处理业务校验过程中碰到的非法参数异常 该异常基本由{@link Assert}抛出
	 * @param exception 参数校验异常
	 * @return API返回结果对象包装后的错误输出结果
	 * @see Assert#hasLength(String, String)
	 * @see Assert#hasText(String, String)
	 * @see Assert#isTrue(boolean, String)
	 * @see Assert#isNull(Object, String)
	 * @see Assert#notNull(Object, String)
	 */
	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseStatus(HttpStatus.OK)
	public R handleIllegalArgumentException(IllegalArgumentException exception) {
		log.error("非法参数,ex = {}", exception.getMessage(), exception);
		return R.fail(exception.getMessage());
	}

	@ExceptionHandler({ BindException.class })
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Void> bindErrorHandler(BindException ex) {
		return getBindErrorResponse(ex.getBindingResult());
	}

	@ExceptionHandler({ MethodArgumentNotValidException.class })
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Void> argvBindErrorHandler(MethodArgumentNotValidException ex) {
		return getBindErrorResponse(ex.getBindingResult());
	}

	@ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
	@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
	public R<Void> errorHandler(HttpRequestMethodNotSupportedException ex) {
		return R.fail(SoyMvcStatusCode.METHOD_NOT_SUPPORTED.getCode(),
				String.format("错误的请求：不支持[%s]请求", ex.getMethod()));
	}

	@ExceptionHandler({ MultipartException.class })
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Void> multipartException(MultipartException ex) {
		return R.fail(SoyMvcStatusCode.BAD_REQUEST.getCode(), "错误的请求，解析请求内容失败，" + ex.getMessage());
	}

	@ExceptionHandler({ HttpMessageNotReadableException.class })
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Void> errorHttpMessageNotReadable(HttpMessageNotReadableException e) {
		return R.fail(SoyMvcStatusCode.MISS_BODY.getCode(), "读取请求体异常");
	}

	@ExceptionHandler({ HttpMediaTypeNotSupportedException.class })
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Void> errorHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException e) {
		return R.fail(SoyMvcStatusCode.BAD_REQUEST.getCode(), "当前不支持处理该请求类型");
	}

	@ExceptionHandler({ MissingServletRequestParameterException.class })
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Void> errorHttpMediaTypeNotSupported(MissingServletRequestParameterException e) {
		return R.fail(SoyMvcStatusCode.MISS_PARAMETER.getCode(),
				String.format("%s：%s", SoyMvcStatusCode.MISS_PARAMETER.getMessage(), e.getMessage()));
	}

	@ExceptionHandler({ NoHandlerFoundException.class })
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public R<Void> default404Handler(NoHandlerFoundException e) {
		return R.fail(SoyMvcStatusCode.URL_NOT_FOUND.getCode(), e.getMessage());
	}

	@ExceptionHandler({ ServerErrorException.class })
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public R<Void> serverErrorException(ServerErrorException e) {
		log.error("捕获到ServerErrorException", e);
		return R.fail(CommonStatusCode.ERROR.getCode(), e.getMessage());
	}

	/**
	 * 保持和低版本请求路径不存在的行为一致
	 * <p>
	 * <a href="https://github.com/spring-projects/spring-boot/issues/38733">[Spring Boot
	 * 3.2.0] 404 Not Found behavior #38733</a>
	 * @param exception
	 * @return R
	 */
	@ExceptionHandler({ NoResourceFoundException.class })
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public R bindExceptionHandler(NoResourceFoundException exception) {
		log.debug("请求路径 404 {}", exception.getMessage());
		return R.fail(exception.getMessage());
	}

	@ExceptionHandler({ SoyRuntimeException.class })
	public R<Void> defaultErrorHandler(SoyRuntimeException e, HttpServletResponse response) {
		if (log.isDebugEnabled()) {
			log.debug("捕获到SoyRuntimeException{}", e.getMessage());
		}
		response.setStatus(HttpStatus.OK.value());
		SoyStatusCode status = e.getStatus();
		if (Objects.nonNull(status)) {
			Integer activeHttpCode = StatusCodeUtils.getActiveHttpCode(status);
			if (Objects.nonNull(activeHttpCode)) {
				response.setStatus(activeHttpCode.intValue());
			}
		}
		return getResponse(e.getStatus(), e.getMessage());
	}

	private R<Void> getResponse(SoyStatusCode code, String message) {
		return R.fail(Objects.requireNonNullElse(code.getCode(), CommonStatusCode.ERROR.getCode()), message);
	}

	private R<Void> getBindErrorResponse(BindingResult bindingResult) {
		StringBuilder errorMsg = new StringBuilder();
		for (ObjectError error : bindingResult.getAllErrors()) {
			errorMsg.append(error.getDefaultMessage()).append(",");
		}
		errorMsg.delete(errorMsg.length() - 1, errorMsg.length());
		return R.fail(CommonStatusCode.ARG_INVALID.getCode(), errorMsg.toString());
	}

}
